#
# Copyright 2009 Red Hat, Inc. and/or its affiliates.
#
# Licensed to you under the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.  See the files README and
# LICENSE_GPL_v2 which accompany this distribution.
#

import os.path
import threading

import constants
import misc
import pv
import logging
import sd
import lvmc
import lvm
import syncspace

import storage_exception as se

LVMBACKUP = "/etc/lvm/backup"

STORAGE_DOMAIN_TAG = "RHAT_storage_domain"

USER_GROUP = "vdsm:kvm"
UNKNOWN_DEVICE = "unknown device"

VG_OK = "OK"
VG_PARTIAL = "PARTIAL"
VG_UNKNOWN = "UNKNOWN"

# VG's min metadata threshold is 20%
MDA_MIN_THRESHOLD = 0.2

MINIMALVGSIZE = 10 * 1024 * 1024 * 1024
class VGOpWouldBlock(Exception):
    pass

class VG:
    """
    VG (Volume Group) interface
    """
    lvm = lvmc.LVMCache
    log = logging.getLogger('irs')

    def __init__(self, name, vgdata=None):
        self.name = name
        self.initialized = False
        self.devices = []
        self.devList = []
        self.uuid = None
        self.attr = ""
        self.vgsize = 0
        self.vgfree = 0
        self.vgextsize = 0
        self.vgextcount = 0
        self.vgmetaratio = 0
        self.mda_size = 0
        self.mda_free = 0
        self.backupconfig = os.path.join(LVMBACKUP, self.name)
        self.hasConfig = os.path.exists(self.backupconfig)
        self.sync = syncspace.SyncSpace(name)
        self.refresh(vgdata)
        self._lvAccessCount = {} # RefCount for every LV access
        # An activity event for every LV access.
        # Cleared if activation or deactivation is in progress.
        self._lvAccessActivity = {}
        self._lvAccessLock = threading.Lock() # Moderates the access to the lvAccess* dicts

    def lockResource(self, obj, exclusive=False, trylock=False):
        # All VG object must at least take shared lock on the VG
        if obj != self.name:
            self.sync.acquire(self.name)
        try:
            r = self.sync.acquire(obj, exclusive=exclusive, trylock=trylock)
        except Exception, e:
            misc.logException(e)
            r = False
        if not r and obj != self.name:
            self.log.debug("releasing implicy vg %s lock" % (self.name))
            self.sync.release(self.name)
        return r

    def unlockResource(self, obj, exclusive=False):
        self.sync.release(obj, exclusive=exclusive)
        # All VG object must at least take shared lock on the VG (see above),
        # so if such implict lock was taken, we need to release it
        if obj != self.name:
            self.sync.release(self.name)

    def syncExecCmd(self, obj, cmd, exclusive=False, trylock=False):
        #self.log.debug("(vg) %s: syncExecCmd obj %s cmd %s ex %s try %s" % (self.name, obj, cmd, exclusive, trylock))
        if not self.lockResource(obj, exclusive=exclusive, trylock=trylock):
            raise VGOpWouldBlock
        try:
            return misc.execCmd(cmd)
        finally:
            self.unlockResource(obj, exclusive=exclusive)
            #self.log.debug("(vg) %s: syncExecCmd obj %s" % (self.name, obj))

    def _refreshDevices(self):
        cmd = [constants.EXT_VGS, "--noheadings", "-o", "pv_name", self.name]
        cmd = lvm.addExtraCfg(cmd)
        (rc, out, err) = self.syncExecCmd(self.name, cmd)
        if rc != 0:
            return False

        devices = [dev.strip() for dev in out]
        if UNKNOWN_DEVICE in devices:
            raise se.InvalidPhysDev()

        # Initialize device list
        self.devices = [pv.PV(dev, lvmFilter=devices) for dev in devices]
        self.devList = devices
        return True

    def metadataValidity(self):
        mdaStatus = {'mdavalid':True, 'mdathreshold':True}
        if not self.mda_size:
            return mdaStatus

        if self.mda_size < pv.METADATASIZE/2:
            mdaStatus['mdavalid'] = False

        if self.mda_size * MDA_MIN_THRESHOLD > self.mda_free:
            mdaStatus['mdathreshold'] = False

        return mdaStatus

    def _refreshVGInfo(self):
        cmd = [constants.EXT_VGS, "--noheadings",
            "-o", "vg_uuid,vg_size,vg_free,vg_extent_size,vg_extent_count,attr,vg_mda_size,vg_mda_free",
            "--nosuffix", "--units", "b", self.name]
        cmd = lvm.addExtraCfg(cmd, self.devList)
        (rc, out, err) = self.syncExecCmd(self.name, cmd)
        # We treat non-zero exit code as a definite probelm idicator
        if rc != 0:
            return False

        # If stderr is not empty we suspect things aren't that good even when
        # rc == 0
        if err:
            for dev in self.devices:
                if not dev.validate():
                    return False

        vgs = out[0].split()
        if len(vgs) < 5:
            return False

        # Fetch VG UUID
        self.uuid = vgs[0]
        self.vgsize = int(vgs[1])
        self.vgfree = int(vgs[2])
        self.vgextsize = int(vgs[3])
        self.vgextcount = int(vgs[4])
        self.attr = vgs[5]
        self.vgmetaratio = self.vgextsize / sd.METASIZE
        self.mda_size = int(vgs[6])
        self.mda_free = int(vgs[7])
        self.initialized = True
        return True

    def _cleanRefresh(self):
        # We are here becuase we believe vg data change, mark lvm cache as stale
        self.lvm.markStaleVg(self.name)
        # Refresh devices
        res = self._refreshDevices()
        if res:
            res = self._refreshVGInfo()

        return res


    def _updateInfo(self, vgdata):
        # Fetch VG UUID
        self.uuid = vgdata['uuid']
        # Initialize device list
        self.devices = [pv.PV(dev, vgdata['pvs'][dev]) for dev in vgdata['pvs']]
        self.devList = [dev for dev in vgdata['pvs']]

        self.attr = vgdata['attr']
        self.vgsize = int(vgdata['size'])
        self.vgfree = int(vgdata['free'])
        self.vgextsize = int(vgdata['extent_size'])
        self.vgextcount = int(vgdata['extent_count'])
        self.vgmetaratio = self.vgextsize / sd.METASIZE
        self.initialized = True
        return True

    def refresh(self, vgdata=None):
        '''
        Refresh VG configuration. Most of it is static and can be subject
        to caching.
        '''
        if not vgdata:
            try:
                rc = self._cleanRefresh()
            except se.InvalidPhysDev:
                lvm.updateLvmConf()
                rc = self._cleanRefresh()
        else:
            rc = self._updateInfo(vgdata)
        return rc

    def listPVS(self):
        '''
        Get PV configuration. Most of it is static and can be subject to caching.
        '''
        keys = ["guid", "uuid", "pecount", "pealloc"]
        pvlist = []

        for i in self.devices:
            pvlist.append(dict(zip(keys,
                (i.guid, i.uuid, i.pecount, i.pealloc))))

        return pvlist

    def __str__(self):
        s = []
        s += ["Name: " + self.name]
        if self.initialized:
            s += ["UUID: " + self.uuid]
            s += ["Device list: "]
            s += [str(dev) for dev in self.devices]
        else:
            s += ["Not initialized"]
        return "\n".join(s)

    def isInitialized(self):
        return self.initialized

    def getState(self):
        # The vg_attr bits are:
        #   1  Permissions: (w)riteable, (r)ead-only
        #   2  Resi(z)eable
        #   3  E(x)ported
        #   4  (p)artial: one or more physical volumes belonging to the volume group are missing from
        #         the system
        #   5  Allocation policy: (c)ontiguous, c(l)ing, (n)ormal, (a)nywhere, (i)nherited
        #   6  (c)lustered
        #
        state = VG_UNKNOWN
        try:
            attr = list(self.attr)
            if len(attr) != 6:
                raise

            if attr[3] == '-':
                state = VG_OK
            elif attr[3] == 'p':
                state = VG_PARTIAL
            else:
                raise
        except Exception:
            self.log.error("(Wrong attr (%s) for vg %s" % (self.attr, self.uuid))

        return state

    def getFirstExt(self, lv):
        cmd = [constants.EXT_LVS, "--noheadings", "-o", "devices", "%s/%s" % (self.name, lv)]
        cmd = lvm.addExtraCfg(cmd, self.devList)
        (rc, out, err) = self.syncExecCmd(lv, cmd)
        if rc or len(out) == 0:
            raise se.GetLogicalVolumeDevError("%s/%s" % (self.name, lv))
        res = out[0].strip(" )").split("(")
        dev = res[0]
        ext = int(res[1])
        return dev, ext

    def rename(self, name):
        cmd = [constants.EXT_VGRENAME, "--autobackup", "n", self.name, name]
        cmd = lvm.addExtraCfg(cmd, self.devList)
        (rc, out, err) = self.syncExecCmd(self.name, cmd, exclusive=True)
        if rc == 0:
            self.name = name
        else:
            raise se.VolumeGroupRenameError(out)

    def create(self, devices):
        """
        Creates new VG out of devices
        """
        if self.initialized:
            raise se.VolumeGroupAlreadyExistsError(self.name)
        # Verify requested VG size.
        req_size = 0
        for dev in devices:
            req_size += int(dev.capacity)
        if req_size < MINIMALVGSIZE:
            raise se.VolumeGroupSizeError("VG size must be more than %s bytes" % str(MINIMALVGSIZE))
        devlist = []
        # First device in the list will carry the metadata,
        # make sure it has one allocated
        dev = devices[0]
        dev.reinitMetadata()
        devlist.append(dev.devname)
        # The rest of devices (if any) will go in as is
        for dev in devices[1:]:
            if not dev.isInitialized():
                dev.initialize()
            devlist.append(dev.devname)
        cmd = [constants.EXT_VGCREATE, self.name, "--physicalextentsize", "128M"] + devlist
        cmd = lvm.addExtraCfg(cmd, devlist)
        rc = self.syncExecCmd(self.name, cmd, exclusive=True)[0]
        if rc != 0:
            raise se.VolumeGroupCreateError(self.name, str(devlist))
        self.devList = devlist
        self.initialized = True

        self.refresh()

    def destroy(self):
        """
        Destroys the underlying VG
        """
        if self.initialized:
            cmd = [constants.EXT_VGREMOVE, "-f", self.name]
            cmd = lvm.addExtraCfg(cmd, self.devList)
            rc = self.syncExecCmd(self.name, cmd, exclusive=True)[0]
            if rc != 0:
                raise se.VolumeGroupRemoveError(self.name)

        self.initialized = False
        self.devices = []
        self.devList = []
        self.uuid = None
        self.vgsize = 0
        self.vgfree = 0

    def listLVS(self):
        cmd = [constants.EXT_LVS, "-o", "name", "--noheadings", self.name]
        cmd = lvm.addExtraCfg(cmd, self.devList)
        (rc, out, err) = self.syncExecCmd(self.name, cmd)
        if rc != 0:
            raise se.LogicalVolumesListError(self.name)
        return [s.strip() for s in out]

    def hasSDTag(self):
        cmd = [constants.EXT_VGS, "-o", "tags", "--noheadings", self.name]
        cmd = lvm.addExtraCfg(cmd, self.devList)
        (rc, out, err) = self.syncExecCmd(self.name, cmd)
        if rc != 0:
            raise se.VolumeGroupReadTagError(self.name)
        if not out or not out[0]:
            return False
        line = out[0].strip()
        tags = line.split(",")
        for t in tags:
            if t == STORAGE_DOMAIN_TAG:
                return True
        return False

    def setSDTag(self):
        if self.isInitialized():
            cmd = [constants.EXT_VGCHANGE, "--addtag", STORAGE_DOMAIN_TAG, self.name]
            cmd = lvm.addExtraCfg(cmd, self.devList)
            rc = self.syncExecCmd(self.name, cmd, exclusive=True)[0]
            if rc != 0:
                raise se.VolumeGroupAddTagError(self.name)
        else:
            raise se.VolumeGroupUninitialized(self.name)

    def remSDTag(self):
        if self.isInitialized():
            cmd = [constants.EXT_VGCHANGE, "--deltag", STORAGE_DOMAIN_TAG, self.name]
            cmd = lvm.addExtraCfg(cmd, self.devList)
            rc = self.syncExecCmd(self.name, cmd, exclusive=True)[0]
            if rc != 0:
                raise se.VolumeGroupRemoveTagError(self.name)
        else:
            raise se.VolumeGroupUninitialized(self.name)

    def addDevice(self, device):
        """
        Add new physcial volume `device' to the Volume Group
        """
        if not device.isInitialized():
            device.initialize()

        if self.initialized:
            cmd = [constants.EXT_VGEXTEND, self.name, device.devname]
            cmd = lvm.addExtraCfg(cmd, self.devList + [device.devname])
            rc = self.syncExecCmd(self.name, cmd, exclusive=True)[0]
            if rc != 0:
                raise se.VolumeGroupExtendError(self.name, device.devname)
        else:
            cmd = [constants.EXT_VGCREATE, self.name, device.devname]
            cmd = lvm.addExtraCfg(cmd, [device.devname])
            rc = self.syncExecCmd(self.name, cmd, exclusive=True)[0]
            if rc != 0:
                raise se.VolumeGroupCreateError(self.name, device.devname)
            self.initialized = True

        self.refresh()

    def activate(self):
        cmd = [constants.EXT_CHOWN, USER_GROUP,  os.path.join("/dev", self.name)]
        rc = self.syncExecCmd(self.name, cmd, exclusive=True)[0]
        if rc != 0:
            raise se.VolumeGroupPermissionsError(self.name)

    def deactivate(self):
        cmd = [constants.EXT_VGCHANGE, "--available", "n", "%s" % (self.name)]
        cmd = lvm.addExtraCfg(cmd, self.devList)
        (rc, out, err) = self.syncExecCmd(self.name, cmd, exclusive=True)
        if rc != 0:
            # we ignore error here becuase we don't care about this vg anymore
            self.log.info("deactivate %s failed: rc %s - %s (ignored)" % (self.name, rc, out))

    def getDeviceList(self):
        return self.devices

    def backupConfig(self):
        if self.hasConfig:
            # Backup the VG configuration
            cmd = [constants.EXT_VGCFGBACKUP, self.name]
            cmd = lvm.addExtraCfg(cmd, self.devList)
            rc = self.syncExecCmd(self.name, cmd, exclusive=True)[0]
            if rc == 0 and os.path.exists(self.backupconfig):
                self.initialized = True

    def exportConfig(self):
        if self.hasConfig:
            config = open(self.backupconfig).readlines()
        return config

    def createLV(self, name, size, activate=True, cont=False):
        if self.accessLV(name):
            raise se.LogicalVolumeAlreadyExists("vg %s lv %s", (self.name, name))
        try:
            if misc.parseHumanReadableSize(size) > self.vgfree:
                raise se.VolumeGroupSizeError("Need %s of free space" % size)

            c = {True:"y", False:"n"}[cont]
            cmd = [constants.EXT_LVCREATE, "--autobackup", "n", "--contiguous", c, "--size", "%s" % size, "--name", name, self.name]
            cmd = lvm.addExtraCfg(cmd, self.devList)
            rc = self.syncExecCmd(self.name, cmd, exclusive=True)[0]
            if rc != 0:
                raise se.CannotCreateLogicalVolume(name)
            else:
                # Update internal VG bookkeeping info
                self.refresh()
            cmd = [constants.EXT_CHOWN, USER_GROUP,  os.path.join("/dev", self.name, name)]
            rc = misc.execCmd(cmd)[0]
            if rc != 0:
                raise se.LogicalVolumePermissionsError(name)

            self.log.debug("CREATELV: ext %s/%s" % (self.getFirstExt(name)))
            if not activate:
                self.deactivateLV(name)
        except Exception:
            try:
                if self.accessLV(name):
                    self.removeLV(name)
            except:
                self.log.error("createLV cleanup failed: %s", self.name)
            raise

    def removeLV(self, name):
        cmd = [constants.EXT_LVREMOVE, "--autobackup", "n", "-f", "%s/%s" % (self.name, name)]
        cmd = lvm.addExtraCfg(cmd, self.devList)
        rc = self.syncExecCmd(self.name, cmd, exclusive=True)[0]
        if rc != 0:
            raise se.CannotRemoveLogicalVolume(name)
        else:
            # Update internal VG bookkeeping info
            self.refresh()

    def extendLV(self, name, newsize):
        """
        Extend the LV name up to the size of newsize MB
        """
        try:
            lvdata = self.lvm.getLV(self.name, name)
            if int(newsize) * 1024 * 1024 <= int(lvdata['size']):
                self.log.debug("extendLV - volume: %s requested size(%sM) <= lv size(%sM)" % (name, newsize, int(lvdata['size'])/1024/1024))
                return True
        except:
            pass

        cmd = [constants.EXT_LVEXTEND, "--autobackup", "n", "--size", "%sM" % newsize, "%s/%s" % (self.name, name)]
        cmd = lvm.addExtraCfg(cmd, self.devList)
        rc = self.syncExecCmd(name, cmd, exclusive=True)[0]

        if rc != 0 and (self.lvSize(name) / (1024*1024)) < newsize:
            raise se.LogicalVolumeExtendError(self.name, name, newsize)
        # Update internal VG bookkeeping info
        self.refresh()
        return True

    def refreshLV(self, name):
        """
        Refresh LVM internal information cache - used when LV has been changed
        somewhere else
        """
        _cmd = [constants.EXT_LVCHANGE, "--refresh", "%s/%s" % (self.name, name)]
        cmd = lvm.addExtraCfg(_cmd, self.devList)
        # lvchange takes a write lock on the VG, therefore we need to do it too
        (rc, out, err) = self.syncExecCmd(self.name, cmd, exclusive=True)
        if rc != 0:
            self.refresh()
            cmd = lvm.addExtraCfg(_cmd, self.devList)
            rc = self.syncExecCmd(self.name, cmd, exclusive=True)[0]
            if rc != 0:
                raise se.LogicalVolumeRefreshError(name)


    def accessLVs(self, names, rw):
        lvlist = ["%s/%s" % (self.name, name) for name in names]
        cmd = [constants.EXT_LVS, "-o", "name,attr", "--noheadings"] + lvlist
        cmd = lvm.addExtraCfg(cmd, self.devList)
        (rc, out, err) = self.syncExecCmd(self.name, cmd)
        if rc != 0:
            self.log.warning("Could not access one or more lvs on vg (%s): %s" % (self.name, names))
        if len(out) < 1:
            raise se.LogicalVolumesScanError(self.name, names)
        lvs = {}
        for lv in [line.strip() for line in out]:
            name,attr = lv.split()
            lvs[name] = attr
        self.log.debug("LVS ACCESS: %s" % (lvs))
        badlvs = []
        for name in names:
            if not lvs.has_key(name):
                badlvs.append(name)
            elif rw and lvs[name][1] != "w":
                badlvs.append(name)
        return badlvs


    def accessLV(self, name, rw=None):
        cmd = [constants.EXT_LVS, "-o", "attr", "--noheadings", "%s/%s" % (self.name, name)]
        cmd = lvm.addExtraCfg(cmd, self.devList)
        (rc, out, err) = self.syncExecCmd(name, cmd)
        s = ""
        if len(out) >= 1:
            s = out[0].strip()
        if not rw or rc != 0:
            self.log.debug("ACCESS RD: lv %s %s (%s)" % (name, rc, s))
            return rc == 0
        if len(out) < 1:
            raise se.LogicalVolumeScanError(self.name, name)
        self.log.debug("ACCESS WR: lv %s %s" % (name, s))
        return s[1] == "w"

    def lvOpened(self, name):
        cmd = [constants.EXT_LVS, "-o", "attr", "--noheadings", "%s/%s" % (self.name, name)]
        cmd = lvm.addExtraCfg(cmd, self.devList)
        (rc, out, err) = self.syncExecCmd(name, cmd)
        if rc != 0:
            self.log.debug("LVOPENED RD: lv %s %s" % (name, rc))
            return rc == 0
        s = out[0].strip()
        return s[-1:] == "o"

    def setrwLV(self, name, rw):
        self.log.info("setrwLV %s RW %s" % (name, rw))
        currw = self.accessLV(name, rw=True)
        if currw == rw:
            return  # Nothing to do
        perm = "r"
        if rw:
            perm = "rw"
        cmd = [constants.EXT_LVCHANGE, "-p", perm, "%s/%s" % (self.name, name)]
        cmd = lvm.addExtraCfg(cmd, self.devList)
        rc = self.syncExecCmd(self.name, cmd, exclusive=True)[0]
        if rc:
            raise se.CannotSetRWLogicalVolume(self.name, name, rw)

    def activateLVs(self, names, rw=True):
        errors = False
        badlvs = self.accessLVs(names, rw)
        if len(badlvs):
            errors = True
            goodlvs = [lv for lv in names if lv not in badlvs]
        else:
            goodlvs = names
        lvlist = ["%s/%s" % (self.name, name) for name in goodlvs]
        cmd = [constants.EXT_LVCHANGE, "--available", "y"] + lvlist
        cmd = lvm.addExtraCfg(cmd, self.devList)
        rc = self.syncExecCmd(self.name, cmd)[0]
        # Note: you may activate an activated LV without error
        if rc:
            self.refresh()
            cmd = [constants.EXT_LVCHANGE, "--available", "y"] + lvlist
            cmd = lvm.addExtraCfg(cmd, self.devList)
            rc = self.syncExecCmd(self.name, cmd)[0]
            if rc:
                #raise se.CannotActivateLogicalVolume(name)
                errors = True
                self.log.warning("Could not activate one or more volumes in vg (%s) - %s" % (self.name, goodlvs))

        vgdir = os.path.join("/dev", self.name)
        paths = [os.path.join("/dev", lv) for lv in lvlist]
        cmd = [constants.EXT_CHOWN, USER_GROUP, vgdir] + paths
        if misc.execCmd(cmd)[0] != 0:
            auxcmd = [constants.EXT_VGMKNODES, self.name]
            misc.execCmd(auxcmd)
            if misc.execCmd(cmd)[0] != 0:
                errors = True
                self.log.warning("Could not change ownership of one or more volumes in vg (%s) - %s" % (self.name, goodlvs))
        if errors:
            raise se.CannotActivateLogicalVolumes(names)

    def activateLV(self, name, rw=True):
        self._lvAccessLock.acquire()
        try:
            while name in self._lvAccessActivity:
                waitForLv = self._lvAccessActivity[name]
                if waitForLv.isSet():
                    break

                self._lvAccessLock.release()
                waitForLv.wait()
                self._lvAccessLock.acquire()

            if name in self._lvAccessCount:
                self._lvAccessCount[name] += 1
                self._lvAccessActivity[name].clear()
            else:
                self._lvAccessCount[name] = 1
                self._lvAccessActivity[name] = threading.Event()
        finally:
            if self._lvAccessLock.locked():
                self._lvAccessLock.release()

        try:
            # lvchange returns an error (RC=5) when activating rw if already rw
            self.log.info("activate %s RW %s" % (name, rw))
            currw = self.accessLV(name, rw=True)
            if currw != rw and rw:
                raise se.CannotWriteAccessLogialVolume(self.name, name)
            cmd = [constants.EXT_LVCHANGE, "--available", "y", "%s/%s" % (self.name, name)]
            cmd = lvm.addExtraCfg(cmd, self.devList)
            rc = self.syncExecCmd(name, cmd)[0]
            # Note: you may activate an activated LV without error
            if rc:
                self.refresh()
                cmd = [constants.EXT_LVCHANGE, "--available", "y", "%s/%s" % (self.name, name)]
                cmd = lvm.addExtraCfg(cmd, self.devList)
                rc = self.syncExecCmd(name, cmd)[0]
                if rc:
                    raise se.CannotActivateLogicalVolume(name)

            # Fix volume ownership
            path = os.path.join("/dev", self.name, name)
            cmd = [constants.EXT_CHOWN, USER_GROUP, path]
            if misc.execCmd(cmd)[0] != 0:
                auxcmd = [constants.EXT_VGMKNODES, self.name]
                misc.execCmd(auxcmd)
                if misc.execCmd(cmd)[0] != 0:
                    raise se.LogicalVolumePermissionsError(name)

            self._lvAccessActivity[name].set()
        except:
            self._lvAccessCount[name] -= 1
            evt = self._lvAccessActivity[name]
            if self._lvAccessCount[name] == 0:
                del self._lvAccessCount[name]
                del self._lvAccessActivity[name]
            evt.set()
            raise

    def renameLV(self, volUUID, newUUID):
        self.log.info("VG %s: rename LV %s to %s" % (self.name, volUUID, newUUID))
        cmd = [constants.EXT_LVRENAME, self.name, volUUID, newUUID]
        cmd = lvm.addExtraCfg(cmd, self.devList)
        (rc, out, err) = self.syncExecCmd(self.name, cmd, exclusive=True)
        if rc != 0:
            raise se.LogicalVolumeRenameError(volUUID)

    def changeLVTag(self, lv, oldTag, newTag):
        self.untagLV(lv, oldTag)
        self.tagLV(lv, newTag)

    def tagLV(self, lv, tag):
        cmd = [constants.EXT_LVCHANGE, "--addtag", tag, "%s/%s" % (self.name, lv)]
        cmd = lvm.addExtraCfg(cmd, self.devList)
        rc = self.syncExecCmd(self.name, cmd, exclusive=True)[0]
        if rc != 0:
            raise se.LogicalVolumeAddTagError(lv)

    def untagLV(self, lv, tag):
        cmd = [constants.EXT_LVCHANGE, "--deltag", tag, "%s/%s" % (self.name, lv)]
        cmd = lvm.addExtraCfg(cmd, self.devList)
        rc = self.syncExecCmd(self.name, cmd, exclusive=True)[0]
        if rc != 0:
            raise se.LogicalVolumeRemoveTagError(lv)

    def lvsByTag(self, tag):
        cmd = [constants.EXT_LVS, "--noheadings", "-o",  "name,vg_name", "@%s" % tag]
        cmd = lvm.addExtraCfg(cmd, self.devList)
        (rc, out, err) = self.syncExecCmd(self.name, cmd)
        if rc:
            raise se.GetLogicalVolumesByTagError(tag)
        lv_list = []
        for l in out:
            lv, vg = l.split()
            if vg.strip() == self.name:
                lv_list.append(lv.strip())
        return lv_list


    def lvPath(self, lv):
        '''
        Return the device name of the LV
        '''
        return os.path.join("/dev", self.name, lv)

    def listTags(self):
        '''
        Return list of (lists of ) lv name and tags
        '''
        cmd = [constants.EXT_LVS, "--noheadings", "-o", "name,tags", "%s" % (self.name)]
        cmd = lvm.addExtraCfg(cmd, self.devList)
        (rc, out, err) = self.syncExecCmd(self.name, cmd)
        if rc:
            raise se.GetAllLogicalVolumeTagsError(self.name)
        tl = []
        for l in out:
            p = l.split()
            if len(p) < 2:
                continue
            tl.append([p[0]] + [ t.strip() for t in p[1].split(",")])
        return tl

    def listLVTags(self, lv):
        cmd = [constants.EXT_LVS, "--noheadings", "-o", "tags", "%s/%s" % (self.name, lv)]
        cmd = lvm.addExtraCfg(cmd, self.devList)
        (rc, out, err) = self.syncExecCmd(lv, cmd, exclusive=True)
        if rc:
            raise se.GetLogicalVolumeTagError(lv)
        return [ t.strip() for t in out[0].split(",")]

    def deactivateLV(self, name):
        '''
        Remove LV's mapping
        '''
        self._lvAccessLock.acquire()
        evt = None
        try:
            while name in self._lvAccessActivity:
                waitForLv = self._lvAccessActivity[name]
                if waitForLv.isSet():
                    break

                self._lvAccessLock.release()
                waitForLv.wait()
                self._lvAccessLock.acquire()

            if name in self._lvAccessCount:
                self._lvAccessCount[name] -= 1
            else:
                self._lvAccessActivity[name] = threading.Event()
                self._lvAccessCount[name] = 0

            if self._lvAccessCount[name] != 0:
                return

            evt = self._lvAccessActivity[name]
            evt.clear()

        finally:
            if self._lvAccessLock.locked():
                self._lvAccessLock.release()

        try:
            # lvchange returns an error (RC=5) when deactivating 'r' if already 'r'
            self.log.info("deactivateLV %s" % (name))
            cmd = [constants.EXT_LVCHANGE, "--available", "n", "%s/%s" % (self.name, name)]
            cmd = lvm.addExtraCfg(cmd, self.devList)
            rc = None
            try:
                rc = self.syncExecCmd(name, cmd, trylock=True)[0]
            except VGOpWouldBlock:
                self.log.info("deactivateLV %s delayed - used by others" % (name))
            # Note: you may deactivate an deactivated LV without error
            if rc and not self.lvOpened(name):
                raise se.CannotDeactivateLogicalVolume(name)
        finally:
            del self._lvAccessCount[name]
            del self._lvAccessActivity[name]
            evt.set()

    def lvSize(self, name):
        """
        Return size of the given LV.
        We may need to extend it in the future to extract more info
        """
        cmd = [constants.EXT_LVS, "--noheadings", "-o", "lv_size", "--units", "b", "--nosuffix", "%s/%s" % (self.name, name)]
        cmd = lvm.addExtraCfg(cmd, self.devList)
        (rc, out, err) = self.syncExecCmd(name, cmd, exclusive=True)
        if rc != 0:
            raise se.CannotAccessLogicalVolume(name)
        else:
            return int(out[0].strip())

    def getLVInfo(self, name):
        return self.lvSize(name)


    def validate(self):
        """
        Make sure the VG is readable and ready to use
        """
        return self._refreshVGInfo()


def rescan():
    cmd = [constants.EXT_VGSCAN]
    cmd = lvm.addExtraCfg(cmd)
    # do not raise an exception if vgscan fails - it the upper level
    # responsability to decide if it is an error or not
    return misc.execCmd(cmd)[0]

def isReservedVG(name):
    """
    Checks if the name belongs to any reserved vg (i.e. for any system usage)
    """
    # Right night do a naive name check:
    # names "vg0", "system", "HostVG" and "VolGroup00" are considered reserved
    reserved = ["system", "vg0", "HostVG", "VolGroup00"]
    return name in reserved

def getVGList(sdonly=False):
    rescan()
    VG.lvm.refresh()

    l = []
    for v in VG.lvm.getVGNames():
        vgdata = VG.lvm.lookup(v)
        # VG without tag may need further scrutiny
        if STORAGE_DOMAIN_TAG not in vgdata['tags']:
            # If they want only our Storage Domains or this vg contains some
            # any LV, or the name is reserved, or the size isn't large enough -
            # skip it
            if sdonly or len(vgdata['lvs']) != 0 or isReservedVG(v) or vgdata['size'] < MINIMALVGSIZE:
                continue
        vg = VG(v, vgdata)
        l.append(vg)

    return l

def getVGType(vg):
    """
    VG will declared as FCP if all its devices are pure FCP devices (not Mixed)
    VG will declared as iSCSI if all its devices are iSCSI or Mixed devices
    VG with different device types (pure FCP or pure iSCSI) will declared as UNSUPPORTED
    """
    vgType = sd.UNKNOWN_DOMAIN
    iscsiType = False
    fcpType = False
    for dev in vg.devices:
        devInfo = dev.getInfo()
        if devInfo['devtype'] == 'FCP':
            fcpType = True
        else:
            iscsiType = True

    if fcpType:
        if not iscsiType:
            vgType = sd.FCP_DOMAIN
        else:
            vgType = sd.UNKNOWN_DOMAIN
    else:
        vgType = sd.ISCSI_DOMAIN

    return vgType
